﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Authentication;
using System.ServiceModel;
using Informant.Contacts;
using Informant.ErrorHandling;
using Informant.Google.Contacts;
using Informant.Google.Voice;
using Informant.Services.CommonFaults;
using Informant.Services.Contacts;
using Informant.Services.Models.Contacts;
using Informant.Services.Models.SmsMessaging;
using Informant.Services.SmsMessaging;
using Informant.SmsMessaging;

namespace Informant.Services
{
	[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
	[ServiceKnownType(typeof(InformationKind))]
	public class SmsService : IContactService, ISmsService, IDisposable
	{
		IGoogleContactsClient _contactsClient;
		IGoogleVoiceClient _voiceClient;
		IContactServiceCallback _contactServiceCallbackChannel;
		ISmsServiceCallback _smsServiceCallbackChannel;

		#region IContactService Members

		/// <summary>
		/// Logs the on contact service.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public void LogOnContactService(String username, String password)
		{
			InitializeContactService();
			Boolean result = false;
			try
			{
				result = _contactsClient.LogOn(username, password);
			}
			catch (AuthenticationException ex)
			{
				ResetContactService();
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				ResetContactService();
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				ResetContactService();
				throw new FaultException(ex.Message);
			}
			_contactServiceCallbackChannel.LogOnContactServiceCallback(result);
		}
		/// <summary>
		/// Logs the user off the contact service.
		/// </summary>
		public void LogOffContactService()
		{
			Boolean result = false;
			try
			{
				result = _contactsClient.LogOff();
			}
			catch (AuthenticationException ex)
			{
				ResetContactService();
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				ResetContactService();
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				ResetContactService();
				throw new FaultException(ex.Message);
			}
			_contactServiceCallbackChannel.LogOffContactServiceCallback(result);
		}
		/// <summary>
		/// Retrieves all contacts.
		/// </summary>
		public void RetrieveAllContacts()
		{
			IEnumerable<ContactServiceModel> contacts = null;
			try
			{
				contacts = ConvertToServiceObject(_contactsClient.RetrieveAllContacts());
			}
			catch (AuthenticationException ex)
			{
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				throw new FaultException(ex.Message);
			}
			_contactServiceCallbackChannel.RetrieveAllContactsCallback(contacts);
		}
		/// <summary>
		/// Retrieves all groups.
		/// </summary>
		public void RetrieveAllGroups()
		{
			IEnumerable<GroupServiceModel> groups = null;
			try
			{
				groups = ConvertToServiceObject(_contactsClient.RetrieveAllGroups());
			}
			catch (AuthenticationException ex)
			{
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				throw new FaultException(ex.Message);
			}
			_contactServiceCallbackChannel.RetrieveAllGroupsCallback(groups);
		}

		#endregion

		#region ISmsService Members

		/// <summary>
		/// Logs the user on the SMS service.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public void LogOnSmsService(String username, String password)
		{
			InitializeSmsService();
			Boolean result = false;
			try
			{
				result = _voiceClient.LogOn(username, password);
			}
			catch (AuthenticationException ex)
			{
				ResetSmsService();
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				ResetSmsService();
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				ResetSmsService();
				throw new FaultException(ex.Message);
			}
			_smsServiceCallbackChannel.LogOnSmsServiceCallback(result);
		}
		/// <summary>
		/// Logs the user off the SMS service.
		/// </summary>
		public void LogOffSmsService()
		{
			Boolean result = false;
			try
			{
				result = _voiceClient.LogOff();
			}
			catch (AuthenticationException ex)
			{
				ResetSmsService();
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				ResetSmsService();
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				ResetSmsService();
				throw new FaultException(ex.Message);
			}
			_smsServiceCallbackChannel.LogOffSmsServiceCallback(result);
		}
		/// <summary>
		/// Deletes the SMS.
		/// </summary>
		/// <param name="smsId">The SMS id.</param>
		public void DeleteSms(String smsId)
		{
			Boolean result = false;
			try
			{
				result = _voiceClient.DeleteSms(smsId);
			}
			catch (AuthenticationException ex)
			{
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				throw new FaultException(ex.Message);
			}
			_smsServiceCallbackChannel.DeleteSmsCallback(smsId, result);
		}
		/// <summary>
		/// Marks the read SMS.
		/// </summary>
		/// <param name="smsId">The SMS id.</param>
		/// <param name="isRead">if set to <c>true</c> [is read].</param>
		public void MarkReadSms(String smsId, Boolean isRead)
		{
			Boolean result = false;
			try
			{
				result = _voiceClient.MarkReadSms(smsId, isRead);
			}
			catch (AuthenticationException ex)
			{
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				throw new FaultException(ex.Message);
			}
			_smsServiceCallbackChannel.MarkReadSmsCallback(smsId, result);
		}
		/// <summary>
		/// Retrieves all SMS.
		/// </summary>
		public void RetrieveAllSms()
		{
			IEnumerable<SmsServiceModel> allSms = null;
			try
			{
				allSms = ConvertToServiceObject(_voiceClient.RetrieveAllSms());
			}
			catch (AuthenticationException ex)
			{
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				throw new FaultException(ex.Message);
			}
			_smsServiceCallbackChannel.RetrieveAllSmsCallback(allSms);
		}
		/// <summary>
		/// Sends the SMS.
		/// </summary>
		/// <param name="sms">The SMS.</param>
		public void SendSms(SmsServiceModel sms)
		{
			Boolean result = false;
			try
			{
				result = _voiceClient.SendSms(sms);
			}
			catch (SmsException ex)
			{
				throw new FaultException<SendSmsFault>(new SendSmsFault(ex.Message, ConvertToServiceObject(ex.RejectedRecipients)));
			}
			catch (AuthenticationException ex)
			{
				throw new FaultException<AuthorizationFault>(new AuthorizationFault(ex.Message));
			}
			catch (WebException ex)
			{
				throw new FaultException(ex.Message);
			}
			catch (InvalidOperationException ex)
			{
				throw new FaultException(ex.Message);
			}
			_smsServiceCallbackChannel.SendSmsCallback(result);
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="iEnumerable">The i enumerable.</param>
		/// <returns></returns>
		private IEnumerable<ContactServiceModel> ConvertToServiceObject(IEnumerable<IContact> contacts)
		{
			List<ContactServiceModel> serviceObjects = new List<ContactServiceModel>();
			foreach (IContact contact in contacts)
			{
				serviceObjects.Add(ConvertToServiceObject(contact));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="contact">The contact.</param>
		/// <returns></returns>
		private ContactServiceModel ConvertToServiceObject(IContact contact)
		{
			return new ContactServiceModel
			{
				FirstName = contact.FirstName,
				Id = contact.Id,
				LastName = contact.LastName,
				ServiceModelAddresses = ConvertToServiceObject(contact.Addresses),
				ServiceModelEmails = ConvertToServiceObject(contact.Emails),
				ServiceModelGroups = ConvertToServiceObject(contact.Groups),
				ServiceModelOrganizations = ConvertToServiceObject(contact.Organizations),
				ServiceModelPhones = ConvertToServiceObject(contact.Phones)
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="iEnumerable">The i enumerable.</param>
		/// <returns></returns>
		private IEnumerable<OrganizationServiceModel> ConvertToServiceObject(IEnumerable<IOrganization> organizations)
		{
			List<OrganizationServiceModel> serviceObjects = new List<OrganizationServiceModel>();
			foreach (IOrganization organization in organizations)
			{
				serviceObjects.Add(ConvertToServiceObject(organization));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="organization">The organization.</param>
		/// <returns></returns>
		private OrganizationServiceModel ConvertToServiceObject(IOrganization organization)
		{
			return new OrganizationServiceModel
			{
				Kind = organization.Kind,
				Name = organization.Name,
				Title = organization.Title
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="iEnumerable">The i enumerable.</param>
		/// <returns></returns>
		private IEnumerable<EmailServiceModel> ConvertToServiceObject(IEnumerable<IEmail> emails)
		{
			List<EmailServiceModel> serviceObjects = new List<EmailServiceModel>();
			foreach (IEmail email in emails)
			{
				serviceObjects.Add(ConvertToServiceObject(email));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="email">The email.</param>
		/// <returns></returns>
		private EmailServiceModel ConvertToServiceObject(IEmail email)
		{
			return new EmailServiceModel
			{
				Address = email.Address,
				Kind = email.Kind
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="iEnumerable">The i enumerable.</param>
		/// <returns></returns>
		private IEnumerable<AddressServiceModel> ConvertToServiceObject(IEnumerable<IAddress> addresses)
		{
			List<AddressServiceModel> serviceObjects = new List<AddressServiceModel>();
			foreach (IAddress address in addresses)
			{
				serviceObjects.Add(ConvertToServiceObject(address));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="address">The address.</param>
		/// <returns></returns>
		private AddressServiceModel ConvertToServiceObject(IAddress address)
		{
			return new AddressServiceModel
			{
				Kind = address.Kind,
				RawAddress = address.RawAddress
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="iEnumerable">The i enumerable.</param>
		/// <returns></returns>
		private IEnumerable<GroupServiceModel> ConvertToServiceObject(IEnumerable<IGroup> groups)
		{
			List<GroupServiceModel> serviceObjects = new List<GroupServiceModel>();
			foreach (IGroup group in groups)
			{
				serviceObjects.Add(ConvertToServiceObject(group));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="group">The group.</param>
		/// <returns></returns>
		private GroupServiceModel ConvertToServiceObject(IGroup group)
		{
			return new GroupServiceModel
			{
				Id = group.Id,
				Name = group.Name
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="smsMessages">The SMS messages.</param>
		/// <returns></returns>
		private List<SmsServiceModel> ConvertToServiceObject(IEnumerable<ISms> smsMessages)
		{
			List<SmsServiceModel> serviceObjects = new List<SmsServiceModel>();
			foreach (ISms sms in smsMessages)
			{
				serviceObjects.Add(ConvertToServiceObject(sms));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="sms">The SMS.</param>
		/// <returns></returns>
		private SmsServiceModel ConvertToServiceObject(ISms sms)
		{
			return new SmsServiceModel
			{
				Id = sms.Id,
				IsRead = sms.IsRead,
				ServiceModelMessages = ConvertToServiceObject(sms.Messages),
				ServiceModelRecipients = ConvertToServiceObject(sms.Recipients),
				Sender = ConvertToServiceObject(sms.Sender)
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="recipients">The recipients.</param>
		/// <returns></returns>
		private List<PhoneServiceModel> ConvertToServiceObject(IEnumerable<IPhone> recipients)
		{
			List<PhoneServiceModel> serviceObjects = new List<PhoneServiceModel>();
			foreach (IPhone recipient in recipients)
			{
				serviceObjects.Add(ConvertToServiceObject(recipient));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="messages">The messages.</param>
		/// <returns></returns>
		private List<MessageServiceModel> ConvertToServiceObject(IEnumerable<IMessage> messages)
		{
			List<MessageServiceModel> serviceObjects = new List<MessageServiceModel>();
			foreach (IMessage message in messages)
			{
				serviceObjects.Add(ConvertToServiceObject(message));
			}
			return serviceObjects;
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="phone">The phone.</param>
		/// <returns></returns>
		private PhoneServiceModel ConvertToServiceObject(IPhone phone)
		{
			return new PhoneServiceModel
			{
				Kind = phone.Kind,
				Number = phone.Number,
			};
		}
		/// <summary>
		/// Converts to service object.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <returns></returns>
		private MessageServiceModel ConvertToServiceObject(IMessage message)
		{
			return new MessageServiceModel
			{
				ServiceModelSender = message.Sender != null ? ConvertToServiceObject(message.Sender) : null,
				Text = message.Text,
			};
		}
		/// <summary>
		/// Initializes the contact service.
		/// </summary>
		private void InitializeContactService()
		{
			_contactsClient = new GoogleContactsClient();
			_contactServiceCallbackChannel = OperationContext.Current.GetCallbackChannel<IContactServiceCallback>();
		}
		/// <summary>
		/// Initializes the SMS service.
		/// </summary>
		private void InitializeSmsService()
		{
			_voiceClient = new GoogleVoiceClient();
			_voiceClient.SendSmsProgressUpdated += SendSmsProgressUpdatedHandler;
			
			_smsServiceCallbackChannel = OperationContext.Current.GetCallbackChannel<ISmsServiceCallback>();
		}
		/// <summary>
		/// Handles the SendSmsProgressUpdated event.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="Informant.SmsMessaging.SendSmsProgressEventArgs"/> instance containing the event data.</param>
		void SendSmsProgressUpdatedHandler(Object sender, SendSmsProgressEventArgs e)
		{
			_smsServiceCallbackChannel.SendSmsProgressCallback(e.Sent, e.Failed, e.Total);
		}
		/// <summary>
		/// Resets the contact service.
		/// </summary>
		private void ResetContactService()
		{
			_contactsClient = null;
			_contactServiceCallbackChannel = null;
		}
		/// <summary>
		/// Resets the SMS service.
		/// </summary>
		private void ResetSmsService()
		{
			_voiceClient = null;
			_smsServiceCallbackChannel = null;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			if (_contactsClient != null)
			{
				//_contactsClient.LogOff();
				_contactsClient = null;
			}
			if (_voiceClient != null)
			{
				//_voiceClient.LogOff();
				_voiceClient = null;
			}
		}

		#endregion
	}
}
